/* Copyright (c) 2011 Danish Maritime Authority.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dk.dma.ais.message;

import dk.dma.ais.binary.BinArray;
import dk.dma.ais.binary.SixbitEncoder;
import dk.dma.ais.binary.SixbitException;
import dk.dma.ais.sentence.Vdm;
import dk.dma.enav.model.geometry.Position;

/**
 * AIS message 18
 * <p>
 * CLASS B position report implemented according to ITU-R M.1371-4
 */
public class AisMessage18 extends AisMessage implements IVesselPositionMessage {

    /** serialVersionUID. */
    private static final long serialVersionUID = 1L;

    /** Not used. Should be set to zero. Reserved for future use */
    private int spareAfterUserId; // 8 bits

    /**
     * Speed over ground in 1/10 knot steps (0-102.2 knots) 1023 = not available, 1022 = 102.2 knots or higher
     */
    private int sog; // 10 bits

    /**
     * AisPosition Accuracy 1 = high ( =&lt; 10 m) 0 = low (&gt;10 m) 0 = default The PA flag should be determined in
     * accordance with Table 47
     */
    private int posAcc; // 1 bit

    /** Store the positions just as in message 1-3 */
    private AisPosition pos; // : Lat/Long 1/10000 minute

    /**
     * Course over ground in 1/10 = (0-3599). 3600 (E10h) = not available = default; 3601-4095 should not be used
     */
    private int cog; // 12 bits

    /**
     * True heading Degrees (0-359) (511 indicates not available = default)
     */
    private int trueHeading; // 9 bits

    /**
     * Time stamp: UTC second when the report was generated by the EPFS (0-59 or 60 if time stamp is not available,
     * which should also be the default value or 61 if positioning system is in manual input mode or 62 if electronic
     * position fixing system operates in estimated (dead reckoning) mode or 63 if the positioning system is
     * inoperative) 61, 62, 63 are not used by CS AIS
     */
    private int utcSec; // 6 bits : UTC Seconds

    /**
     * Not used. Should be set to zero. Reserved for future use
     */
    private int spare; // 2 bits

    /**
     * Class B unit flag: 0 = Class B SOTDMA unit 1 = Class B CS unit
     */
    private int classBUnitFlag; // 1 bit

    /**
     * Class B display flag: 0 = No display available; not capable of displaying Message 12 and 14 1 = Equipped with
     * integrated display displaying Message 12 and 14
     */
    private int classBDisplayFlag; // 1 bit

    /**
     * Class B DSC flag: 0 = Not equipped with DSC function 1 = Equipped with DSC function (dedicated or time-shared)
     */
    private int classBDscFlag; // 1 bit

    /**
     * Class B band flag: 0 = Capable of operating over the upper 525 kHz band of the marine band 1 = Capable of
     * operating over the whole marine band (irrelevant if Class B Message 22 flag is 0)
     */
    private int classBBandFlag; // 1 bit

    /**
     * 0 = No frequency management via Message 22 , operating on AIS1, AIS2 only 1 = Frequency management via Message 22
     */
    private int classBMsg22Flag; // 1 bit

    /**
     * Mode flag: 0 = Station operating in autonomous and continuous mode = default 1 = Station operating in assigned
     * mode
     */
    private int modeFlag; // 1 bit

    /**
     * RAIM-flag: RAIM (Receiver autonomous integrity monitoring) flag of electronic position fixing device; 0 = RAIM
     * not in use = default; 1 = RAIM in use see Table 47
     */
    private int raim; // 1 bit

    /**
     * Communication state selector flag: 0 = SOTDMA communication state follows 1 = ITDMA communication state follows
     * (always 1 for Class-B CS)
     */
    private int commStateSelectorFlag; // 1 bit

    /**
     * Communication state: SOTDMA communication state (see 3.3.7.2.1, Annex 2), if communication state selector flag is
     * set to 0, or ITDMA communication state (see 3.3.7.3.2, Annex 2), if communication state selector flag is set to 1
     * Because Class B CS does not use any Communication State information, this field should be filled with the
     * following value: 1100000000000000110
     */
    private int commState; // 19 bits : SOTDMA sync state

    /**
     * Instantiates a new Ais message 18.
     */
    public AisMessage18() {
        super(18);
    }

    /**
     * Instantiates a new Ais message 18.
     *
     * @param vdm the vdm
     * @throws AisMessageException the ais message exception
     * @throws SixbitException     the sixbit exception
     */
    public AisMessage18(Vdm vdm) throws AisMessageException, SixbitException {
        super(vdm);
        this.parse();
    }

    /**
     * Parse.
     *
     * @throws AisMessageException the ais message exception
     * @throws SixbitException     the sixbit exception
     */
    public void parse() throws AisMessageException, SixbitException {
        BinArray sixbit = vdm.getBinArray();
        if (sixbit.getLength() != 168) {
            throw new AisMessageException("Message 18 wrong length " + sixbit.getLength());
        }

        super.parse(sixbit);

        this.spareAfterUserId = (int) sixbit.getVal(8);
        this.sog = (int) sixbit.getVal(10);
        this.posAcc = (int) sixbit.getVal(1);
        // Extract position
        this.pos = new AisPosition();
        this.pos.setRawLongitude(sixbit.getVal(28));
        this.pos.setRawLatitude(sixbit.getVal(27));

        this.cog = (int) sixbit.getVal(12);
        this.trueHeading = (int) sixbit.getVal(9);
        this.utcSec = (int) sixbit.getVal(6);
        this.spare = (int) sixbit.getVal(2);

        // Extract class B flags
        this.classBUnitFlag = (int) sixbit.getVal(1);
        this.classBDisplayFlag = (int) sixbit.getVal(1);
        this.classBDscFlag = (int) sixbit.getVal(1);
        this.classBBandFlag = (int) sixbit.getVal(1);
        this.classBMsg22Flag = (int) sixbit.getVal(1);
        this.modeFlag = (int) sixbit.getVal(1);

        // Raim and communication
        this.raim = (int) sixbit.getVal(1);
        this.commStateSelectorFlag = (int) sixbit.getVal(1);
        this.commState = (int) sixbit.getVal(19);
    }

    @Override
    public SixbitEncoder getEncoded() {
        SixbitEncoder encoder = super.encode();
        encoder.addVal(spareAfterUserId, 8);
        encoder.addVal(sog, 10);
        encoder.addVal(posAcc, 1);
        encoder.addVal(pos.getRawLongitude(), 28);
        encoder.addVal(pos.getRawLatitude(), 27);
        encoder.addVal(cog, 12);
        encoder.addVal(trueHeading, 9);
        encoder.addVal(utcSec, 6);
        encoder.addVal(spare, 2);
        encoder.addVal(classBUnitFlag, 1);
        encoder.addVal(classBDisplayFlag, 1);
        encoder.addVal(classBDscFlag, 1);
        encoder.addVal(classBBandFlag, 1);
        encoder.addVal(classBMsg22Flag, 1);
        encoder.addVal(modeFlag, 1);
        encoder.addVal(raim, 1);
        encoder.addVal(commStateSelectorFlag, 1);
        encoder.addVal(commState, 19);
        return encoder;
    }

    /**
     * Gets spare after user id.
     *
     * @return the spareAfterUserId
     */
    public int getSpareAfterUserId() {
        return spareAfterUserId;
    }

    /**
     * Sets spare after user id.
     *
     * @param spareAfterUserId the spareAfterUserId to set
     */
    public void setSpareAfterUserId(int spareAfterUserId) {
        this.spareAfterUserId = spareAfterUserId;
    }

    /**
     * @return the sog
     */
    public int getSog() {
        return sog;
    }

    /**
     * Sets sog.
     *
     * @param sog the sog to set
     */
    public void setSog(int sog) {
        this.sog = sog;
    }

    /**
     * @return the posAcc
     */
    public int getPosAcc() {
        return posAcc;
    }

    /**
     * Sets pos acc.
     *
     * @param posAcc the posAcc to set
     */
    public void setPosAcc(int posAcc) {
        this.posAcc = posAcc;
    }

    /**
     * @return the pos
     */
    public AisPosition getPos() {
        return pos;
    }

    @Override
    public Position getValidPosition() {
        AisPosition pos = this.pos;
        return pos == null ? null : pos.getGeoLocation();
    }

    /**
     * Sets pos.
     *
     * @param pos the pos to set
     */
    public void setPos(AisPosition pos) {
        this.pos = pos;
    }

    /**
     * @return the cog
     */
    public int getCog() {
        return cog;
    }

    /**
     * Sets cog.
     *
     * @param cog the cog to set
     */
    public void setCog(int cog) {
        this.cog = cog;
    }

    /**
     * @return the trueHeading
     */
    public int getTrueHeading() {
        return trueHeading;
    }

    /**
     * Sets true heading.
     *
     * @param trueHeading the trueHeading to set
     */
    public void setTrueHeading(int trueHeading) {
        this.trueHeading = trueHeading;
    }

    /**
     * @return the utcSec
     */
    public int getUtcSec() {
        return utcSec;
    }

    /**
     * Sets utc sec.
     *
     * @param utcSec the utcSec to set
     */
    public void setUtcSec(int utcSec) {
        this.utcSec = utcSec;
    }

    /**
     * Gets spare.
     *
     * @return the spare
     */
    public int getSpare() {
        return spare;
    }

    /**
     * Sets spare.
     *
     * @param spare the spare to set
     */
    public void setSpare(int spare) {
        this.spare = spare;
    }

    /**
     * Gets class b unit flag.
     *
     * @return the classBUnitFlag
     */
    public int getClassBUnitFlag() {
        return classBUnitFlag;
    }

    /**
     * Sets class b unit flag.
     *
     * @param classBUnitFlag the classBUnitFlag to set
     */
    public void setClassBUnitFlag(int classBUnitFlag) {
        this.classBUnitFlag = classBUnitFlag;
    }

    /**
     * Gets class b display flag.
     *
     * @return the classBDisplayFlag
     */
    public int getClassBDisplayFlag() {
        return classBDisplayFlag;
    }

    /**
     * Sets class b display flag.
     *
     * @param classBDisplayFlag the classBDisplayFlag to set
     */
    public void setClassBDisplayFlag(int classBDisplayFlag) {
        this.classBDisplayFlag = classBDisplayFlag;
    }

    /**
     * Gets class b dsc flag.
     *
     * @return the classBDscFlag
     */
    public int getClassBDscFlag() {
        return classBDscFlag;
    }

    /**
     * Sets class b dsc flag.
     *
     * @param classBDscFlag the classBDscFlag to set
     */
    public void setClassBDscFlag(int classBDscFlag) {
        this.classBDscFlag = classBDscFlag;
    }

    /**
     * Gets class b band flag.
     *
     * @return the classBBandFlag
     */
    public int getClassBBandFlag() {
        return classBBandFlag;
    }

    /**
     * Sets class b band flag.
     *
     * @param classBBandFlag the classBBandFlag to set
     */
    public void setClassBBandFlag(int classBBandFlag) {
        this.classBBandFlag = classBBandFlag;
    }

    /**
     * Gets class b msg 22 flag.
     *
     * @return the classBMsg22Flag
     */
    public int getClassBMsg22Flag() {
        return classBMsg22Flag;
    }

    /**
     * Sets class b msg 22 flag.
     *
     * @param classBMsg22Flag the classBMsg22Flag to set
     */
    public void setClassBMsg22Flag(int classBMsg22Flag) {
        this.classBMsg22Flag = classBMsg22Flag;
    }

    /**
     * Gets mode flag.
     *
     * @return the modeFlag
     */
    public int getModeFlag() {
        return modeFlag;
    }

    /**
     * Sets mode flag.
     *
     * @param modeFlag the modeFlag to set
     */
    public void setModeFlag(int modeFlag) {
        this.modeFlag = modeFlag;
    }

    /**
     * @return the raimFlag
     */
    public int getRaim() {
        return raim;
    }

    /**
     * Sets raim.
     *
     * @param raim the raim
     */
    public void setRaim(int raim) {
        this.raim = raim;
    }

    /**
     * Gets comm state selector flag.
     *
     * @return the commStateSelectorFlag
     */
    public int getCommStateSelectorFlag() {
        return commStateSelectorFlag;
    }

    /**
     * Sets comm state selector flag.
     *
     * @param commStateSelectorFlag the commStateSelectorFlag to set
     */
    public void setCommStateSelectorFlag(int commStateSelectorFlag) {
        this.commStateSelectorFlag = commStateSelectorFlag;
    }

    /**
     * Gets comm state.
     *
     * @return the commState
     */
    public int getCommState() {
        return commState;
    }

    /**
     * Sets comm state.
     *
     * @param commState the commState to set
     */
    public void setCommState(int commState) {
        this.commState = commState;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(super.toString());
        builder.append(", classBBandFlag=");
        builder.append(classBBandFlag);
        builder.append(", classBDisplayFlag=");
        builder.append(classBDisplayFlag);
        builder.append(", classBDscFlag=");
        builder.append(classBDscFlag);
        builder.append(", classBMsg22Flag=");
        builder.append(classBMsg22Flag);
        builder.append(", classBUnitFlag=");
        builder.append(classBUnitFlag);
        builder.append(", cog=");
        builder.append(cog);
        builder.append(", commState=");
        builder.append(commState);
        builder.append(", commStateSelectorFlag=");
        builder.append(commStateSelectorFlag);
        builder.append(", modeFlag=");
        builder.append(modeFlag);
        builder.append(", pos=");
        builder.append(pos);
        builder.append(", posAcc=");
        builder.append(posAcc);
        builder.append(", raim=");
        builder.append(raim);
        builder.append(", sog=");
        builder.append(sog);
        builder.append(", spare=");
        builder.append(spare);
        builder.append(", spareAfterUserId=");
        builder.append(spareAfterUserId);
        builder.append(", trueHeading=");
        builder.append(trueHeading);
        builder.append(", utcSec=");
        builder.append(utcSec);
        builder.append("]");
        return builder.toString();
    }

    public boolean isPositionValid() {
        return pos.getGeoLocation() != null;
    }

    public boolean isCogValid() {
        return cog < 3600;
    }

    public boolean isSogValid() {
        return sog < 1023;
    }

    public boolean isHeadingValid() {
        return trueHeading < 360;
    }

}
